package com.abc.xxx.config.security;

import com.abc.xxx.model.User;
import com.abc.xxx.util.Result;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.io.DecodingException;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 负责验证jwt token
 */
@Getter
@Setter
@Slf4j
public class JwtFilter extends OncePerRequestFilter {

    private JwtService jwtService;

    private List<AntPathRequestMatcher> whiteList = new ArrayList<>();

    public JwtFilter(JwtService jwtService, List<String> whiteList) {
        this.jwtService = jwtService;
        if (whiteList != null) {
            whiteList.stream().filter(s-> StringUtils.isNotBlank(s)).forEach(str->{
                this.whiteList.add(new AntPathRequestMatcher(str));
            });
        }
    }

    public boolean isInWhiteList(HttpServletRequest req){
        return this.whiteList.stream().anyMatch(m->m.matches(req));
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException {

        // String uri = req.getRequestURI().replace(req.getContextPath(), "");
        String base = String.format("==>验证token, %s %s -->", req.getMethod(), req.getRequestURI());

        if (isInWhiteList(req)) {
            log.info("{} {}", base, "白名单，通过");
            // 交由后面的过滤器处理
            chain.doFilter(req, res);
        } else {

            // 从请求中解析出jwt token
            String token = jwtService.resolveToken(req);

            if (token == null) {
                log.warn("{} {}", base, "找不到token，不通过");
                responseErrorJson(res, "找不到token，请在Authorization请求头中带上token，例如：'Authorization: Bearer xxx'");
            } else {

                try {
                    // 如果能成功解析出body，证明token没有过期
//                    String preprocessedToken = preprocessToken(req,token);
//                    System.out.println("token: "+token);
//                    System.out.println("preprocessedToken: "+preprocessedToken);
                    Claims body = jwtService.resolveTokenBody(token);
                    log.info("{} {}", base, "验签成功，通过");
                    // jwt token body --> Tenant
                    User user = new User();
                    log.info("jwt body: {}",body.toString());
                    user.parseJwtData(body);
                    System.out.println("jwt filter, user: "+user);

                    // 自动刷新快过期的token
                    boolean nearlyExpire = jwtService.isNearlyExpire(body);
                    if (nearlyExpire) {
                        log.info("{} {}", base, "即将过期，刷新");
                        String newToken = jwtService.refreshToken(body, user.getName(), user.getJwtData());
                        res.setHeader(AuthHeader.ACCESS_TOKEN, newToken);
                    }

                    // User --> Authentication
                    System.out.println("user.getRoleName(): "+user.getRoleName());
                    List<GrantedAuthority> roles = AuthorityUtils.createAuthorityList(user.getRoleName());
                    Authentication auth = new UsernamePasswordAuthenticationToken(user, null, roles);

                    // 从jwt中解析出用户信息，封装成Authentication，存入SecurityContext
                    // 过滤器链中后面的过滤器会从SecurityContext获取Authentication来确定请求是否已登录
                    SecurityContextHolder.getContext().setAuthentication(auth);

                } catch (Exception e) {
                    log.error("{} {} {}", base, "验签失败，不通过",e);
                    if (e instanceof DecodingException){
                        req.setAttribute("jwtError","Token验证失败");
                        throw new BadCredentialsException("Token验证失败");
                    }
                    throw new RuntimeException(e);
                }

                chain.doFilter(req, res);

            }

        }
    }

    private String preprocessToken(HttpServletRequest req, String token){
        String output = token.replace("-", "+").replace("_", "/");
        switch (output.length() % 4) {
            case 0:
                break;
            case 2:
                output += "==";
                break;
            case 3:
                output += "=";
                break;
            default:
                req.setAttribute("jwtError","Token格式错误");
                throw new BadCredentialsException("Token格式错误");
        }
        return output;
    }

    public void responseErrorJson(HttpServletResponse res, String msg) throws IOException {
        Result json = Result.error("jwt filter").msg(msg);
        res.setStatus(401);
        res.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        res.getWriter().write(json.toString());
    }

}
